package artificialNeuralNetworks.ANN;

import java.util.ArrayList;

import debug.Dbg;

/**
 * FileName: NeuralNetwork.java
 * 
 * @Description:
 * 
 * @author Xunhu(Tiger) Sun
 *         email: TigerSun86@gmail.com
 * @date Mar 5, 2014 6:20:29 PM
 */
public class NeuralNetwork {
    public static final String MODULE = "NN";
    public static final boolean DBG = false;

    public final ArrayList<Layer> layers;
    private double learnRate = 0.1;
    private double momentumRate = 0.4;

    public NeuralNetwork(final int nIn, final int[] nHidden,
            final boolean hiddenHasThres, final int nOut,
            final boolean outHasThres) {
        this.layers = new ArrayList<Layer>();
        for (int nH : nHidden) {
            this.addLayer(nIn, nH, hiddenHasThres); // Hidden layer.
        }
        this.addLayer(nIn, nOut, outHasThres); // Output layer.
    }

    public NeuralNetwork(final NeuralNetwork net) {
        this.layers = new ArrayList<Layer>();
        for (Layer l: net.layers){
            this.layers.add(new Layer(l));
        }
        this.learnRate = net.learnRate;
        this.momentumRate = net.momentumRate;
    }

    public void setLearnRate (final double learnRate) {
        this.learnRate = learnRate;
    }

    public void setMomentumRate (final double momentumRate) {
        this.momentumRate = momentumRate;
    }

    private void addLayer (final int nIn, final int unitNum,
            final boolean needThres) {
        final int layerId = layers.size();
        final int attrNum;
        if (layerId == 0) {
            attrNum = nIn;
        } else { // Number of attributes is the number of units in last layer.
            attrNum = layers.get(layers.size() - 1).size();
        }
        layers.add(new Layer(layerId, attrNum, unitNum, needThres));
    }

    public ArrayList<Double> getV (final ArrayList<Double> attrs) {
        Dbg.print(DBG, MODULE, "Input: " + attrs.toString());
        ArrayList<Double> results = attrs;
        for (Layer layer : layers) {
            // Results of one layer is the attributes of next layer.
            results = layer.getV(results);
            Dbg.print(DBG, MODULE, "Layer output: " + results.toString());
        }
        return results;
    }

    public void update (final ANNExampleSet exSet) {
        BackPropagation.learn(this, exSet, learnRate, momentumRate);
    }

    public double getDeltaWeightsSum () {
        double sum = 0;
        for (Layer layer : layers) {
            for (Unit unit : layer.units) {
                for (Double dw : unit.deltaWeights) {
                    sum += Math.abs(dw);
                }
            }
        }
        return sum;
    }
    
    public double difference(final NeuralNetwork net){
        double sum = 0;
        for (int lid =0; lid < layers.size(); lid++) {
            final Layer l = layers.get(lid);
            final Layer otherL = net.layers.get(lid);
            for (int uid = 0; uid< l.units.size(); uid++) {
                final Unit u= l.units.get(uid);
                final Unit otherU= otherL.units.get(uid);
                for (int wid=0; wid< u.weights.size(); wid++){
                    final double w = u.weights.get(wid);
                    final double otherW = otherU.weights.get(wid);
                    sum += Math.abs(w-otherW);
                }
            }
        }
        return sum;
    }

    public Layer getLayerOut () {
        return getLayer(layers.size() - 1);
    }

    public Layer getLayer (final int layerId) {
        return layers.get(layerId);
    }

    public double evaluate (final ANNExampleSet exSet) {
        int count = 0;
        for (ANNExample ex : exSet) {
            ArrayList<Double> result = this.getV(ex.x);
            boolean correct = true;
            for (int i = 0; i < result.size(); i++) {
                double ret = result.get(i);
                double train = ex.t.get(i);
                if (Math.abs(ret - train) > 0.05) {
                    correct = false;
                    break;
                }
            }
            /* System.out.println("train: " + ex.t + " result: " + result
             * + " answer: " + correct); */
            if (correct) {
                count++;
            }
        }
        return (((double) count) / exSet.size());
    }
}
